package edu.northwestern.cbits.purple_robot_manager.config;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.RhinoException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.widget.Toast;
import edu.northwestern.cbits.purple_robot_manager.EncryptionManager;
import edu.northwestern.cbits.purple_robot_manager.PurpleRobotApplication;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.probes.ProbeManager;
import edu.northwestern.cbits.purple_robot_manager.scripting.JavaScriptEngine;
import edu.northwestern.cbits.purple_robot_manager.scripting.SchemeEngine;
import edu.northwestern.cbits.purple_robot_manager.triggers.Trigger;
import edu.northwestern.cbits.purple_robot_manager.triggers.TriggerManager;
public class LegacyJSONConfigFile
{
public static final String FIRST_RUN = "json_config_first_run";
public static final String USER_ID = "user_id";
public static final String JSON_CONFIGURATION = "json_configuration_contents";
public static final String JSON_LAST_UPDATE = "json_configuration_last_update";
public static final String JSON_LAST_HASH = "json_configuration_last_update_hash";
public static final String FEATURES = "features";
private static final String JSON_INIT_SCRIPT = "init_script";
private JSONObject parameters = null;
private static LegacyJSONConfigFile _sharedFile = null;
private static SharedPreferences prefs;
@SuppressLint("DefaultLocale")
public static void updateFromOnline(final Context context)
{
Runnable r = new Runnable()
{
public void run()
{
Runnable next = null;
final EncryptionManager encryption = EncryptionManager.getInstance();
Uri uri = encryption.getConfigUri(context);
if (uri != null && uri.toString().trim().length() > 0)
{
try
{
URL u = new URL(uri.toString());
final SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
Editor edit = prefs.edit();
X509TrustManager trust = new X509TrustManager()
{
public java.security.cert.X509Certificate[] getAcceptedIssuers()
{
return new java.security.cert.X509Certificate[]{};
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
};
TrustManager[] trustAllCerts = { trust };
SSLContext sc = null;
try
{
sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
}
catch (Exception e)
{
LogManager.getInstance(context).logException(e);
}
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
if (conn instanceof HttpsURLConnection)
{
HttpsURLConnection conns = (HttpsURLConnection) conn;
if (prefs.getBoolean("config_http_liberal_ssl", true))
{
conns.setSSLSocketFactory(sc.getSocketFactory());
conns.setHostnameVerifier(new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
});
}
}
for (int z = 0; z < 16 && conn.getHeaderField("Location") != null; z++)
{
URL newUrl = new URL(conn.getHeaderField("Location"));
conn.disconnect();
conn = (HttpURLConnection) newUrl.openConnection();
if (conn instanceof HttpsURLConnection)
{
HttpsURLConnection conns = (HttpsURLConnection) conn;
if (prefs.getBoolean("config_http_liberal_ssl", true))
{
conns.setSSLSocketFactory(sc.getSocketFactory());
conns.setHostnameVerifier(new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
});
}
}
}
BufferedInputStream bin = new BufferedInputStream(conn.getInputStream());
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int read = 0;
while ((read = bin.read(buffer, 0, buffer.length)) != -1)
{
bout.write(buffer, 0, read);
}
bin.close();
String scriptString = new String(bout.toByteArray(), "UTF-8");
String oldHash = prefs.getString(LegacyJSONConfigFile.JSON_LAST_HASH, "");
final String newHash = encryption.createHash(context, scriptString);
PurpleRobotApplication.fixPreferences(context, true);
if (scriptString.toLowerCase().startsWith("(begin")
|| (conn.getContentType() != null && conn.getContentType().toLowerCase()
.startsWith("text/x-scheme")))
{
// TODO: Temp code until we get a more flexible
// parsing system in place...
if ("".equals(scriptString.trim()))
scriptString = "(begin)";
edit.putString("scheme_config_contents", scriptString);
if (oldHash.equals(newHash) == false)
{
try
{
if (Looper.myLooper() == null)
Looper.prepare();
SchemeEngine scheme = new SchemeEngine(context, null);
scheme.evaluateSource(scriptString);
}
catch (final Exception e)
{
LogManager.getInstance(context).logException(e);
e.printStackTrace();
if (context instanceof Activity)
{
final Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(activity, "100: " + e.getMessage(), Toast.LENGTH_LONG)
.show();
}
});
}
}
edit.putString(LegacyJSONConfigFile.JSON_LAST_HASH, newHash);
edit.commit();
if (context instanceof Activity)
{
final Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(activity, R.string.success_json_set_uri, Toast.LENGTH_LONG)
.show();
}
});
}
}
encryption.setConfigurationReady(true);
}
else
{
if ("".equals(scriptString.trim()))
scriptString = "{}";
final JSONObject json = new JSONObject(scriptString);
if (oldHash.equals(newHash) == false)
{
edit.putString(LegacyJSONConfigFile.JSON_CONFIGURATION, scriptString);
edit.commit();
// TriggerManager.getInstance(context).removeAllTriggers();
next = new Runnable()
{
public void run()
{
try
{
if (json.has(LegacyJSONConfigFile.JSON_INIT_SCRIPT))
{
String script = json.getString(LegacyJSONConfigFile.JSON_INIT_SCRIPT);
JavaScriptEngine engine = new JavaScriptEngine(context);
engine.runScript(script);
}
Editor edit = prefs.edit();
edit.putString(LegacyJSONConfigFile.JSON_LAST_HASH, newHash);
edit.commit();
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
catch (final EcmaError e)
{
LogManager.getInstance(context).logException(e);
if (context instanceof Activity)
{
final Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(activity, "200: " + e.getMessage(),
Toast.LENGTH_LONG).show();
}
});
}
}
catch (final EvaluatorException e)
{
LogManager.getInstance(context).logException(e);
if (context instanceof Activity)
{
final Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(activity, "300: " + e.getMessage(),
Toast.LENGTH_LONG).show();
}
});
}
}
}
};
}
encryption.setConfigurationReady(true);
if (context instanceof Activity)
{
final Activity activity = (Activity) context;
activity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(activity, R.string.success_json_set_uri, Toast.LENGTH_LONG)
.show();
}
});
}
}
} catch (IOException e)
{
LogManager.getInstance(context).logException(e);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
catch (Exception e)
{
LogManager.getInstance(context).logException(e);
}
LegacyJSONConfigFile._sharedFile = new LegacyJSONConfigFile(context, next);
}
else
{
final SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
if (prefs.getString(LegacyJSONConfigFile.JSON_CONFIGURATION, "{}").length() > 4)
{
Editor edit = prefs.edit();
edit.putString(LegacyJSONConfigFile.JSON_CONFIGURATION, "{}");
edit.commit();
}
}
}
};
Thread t = new Thread(r);
try
{
t.start();
}
catch (OutOfMemoryError e)
{
System.gc();
LogManager.getInstance(context).logException(e);
}
}
protected void updateTriggers(Context context)
{
List<Trigger> triggerList = new ArrayList<>();
try
{
JSONArray triggers = this.parameters.getJSONArray("triggers");
for (int i = 0; triggers != null && i < triggers.length(); i++)
{
JSONObject json = triggers.getJSONObject(i);
Map<String, Object> map = new HashMap<>();
Iterator<String> keys = json.keys();
while (keys.hasNext())
{
String key = keys.next();
map.put(key, json.get(key));
}
Trigger t = Trigger.parse(context, map);
if (t != null)
triggerList.add(t);
}
}
catch (JSONException e)
{
}
TriggerManager.getInstance(context).updateTriggers(context, triggerList);
}
public static LegacyJSONConfigFile getSharedFile(Context context)
{
SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
if (prefs.getBoolean(LegacyJSONConfigFile.FIRST_RUN, true))
{
EncryptionManager.getInstance().setConfigUri(context,
Uri.parse(context.getString(R.string.json_config_url)));
Editor e = prefs.edit();
e.putBoolean(LegacyJSONConfigFile.FIRST_RUN, false);
e.commit();
}
ProbeManager.allProbes(context);
if (LegacyJSONConfigFile._sharedFile == null)
{
EncryptionManager.getInstance().setConfigurationReady(false);
LegacyJSONConfigFile._sharedFile = new LegacyJSONConfigFile(context, null);
LegacyJSONConfigFile.update(context, false);
}
return LegacyJSONConfigFile._sharedFile;
}
public String getStringParameter(String key)
{
try
{
if (this.parameters != null)
return this.parameters.getString(key);
}
catch (JSONException e)
{
}
return null;
}
private static SharedPreferences getPreferences(Context context)
{
if (LegacyJSONConfigFile.prefs == null)
LegacyJSONConfigFile.prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
return LegacyJSONConfigFile.prefs;
}
private LegacyJSONConfigFile(Context context, Runnable next)
{
SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
try
{
this.parameters = new JSONObject(prefs.getString(LegacyJSONConfigFile.JSON_CONFIGURATION, "{}"));
this.updateSharedPreferences(context);
this.updateTriggers(context);
if (this.parameters.has(LegacyJSONConfigFile.JSON_INIT_SCRIPT))
{
String script = this.parameters.getString(LegacyJSONConfigFile.JSON_INIT_SCRIPT);
JavaScriptEngine engine = new JavaScriptEngine(context);
engine.runScript(script);
}
if (next != null)
next.run();
}
catch (JSONException e)
{
e.printStackTrace();
this.parameters = new JSONObject();
}
catch (RhinoException e)
{
e.printStackTrace();
LogManager.getInstance(context).logException(e);
this.parameters = new JSONObject();
}
}
private void updateSharedPreferences(Context context)
{
String userId = null;
try
{
if (this.parameters.has(LegacyJSONConfigFile.USER_ID))
userId = this.parameters.getString(LegacyJSONConfigFile.USER_ID);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
Editor editor = prefs.edit();
if (userId == null)
userId = EncryptionManager.getInstance().getUserId(context);
if (userId == null)
{
AccountManager manager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
Account[] list = manager.getAccountsByType("com.google");
if (list.length == 0)
list = manager.getAccounts();
if (list.length > 0)
{
userId = list[0].name;
}
}
if (userId != null)
EncryptionManager.getInstance().setUserId(context, userId);
if (this.parameters.has(LegacyJSONConfigFile.FEATURES))
{
ProbeManager.clearFeatures();
try
{
JSONArray features = this.parameters.getJSONArray(LegacyJSONConfigFile.FEATURES);
for (int i = 0; i < features.length(); i++)
{
JSONObject feature = features.getJSONObject(i);
String name = context.getString(R.string.label_unknown_feature);
String script = "";
String formatter = "";
if (feature.has("name"))
name = feature.getString("name");
if (feature.has("feature"))
script = feature.getString("feature");
if (feature.has("formatter"))
formatter = feature.getString("formatter");
ArrayList<String> sources = new ArrayList<>();
if (feature.has("sources"))
{
JSONArray sourceArray = feature.getJSONArray("sources");
for (int j = 0; j < sourceArray.length(); j++)
{
String source = sourceArray.getString(j);
sources.add(source);
}
}
ProbeManager.addFeature(name, LegacyJSONConfigFile.toSlug(name), script, formatter, sources, false);
}
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
}
editor.commit();
}
public static String toSlug(String input)
{
Pattern NONLATIN = Pattern.compile("[^\\w-]");
Pattern WHITESPACE = Pattern.compile("[\\s]");
String nowhitespace = WHITESPACE.matcher(input).replaceAll("_");
String slug = NONLATIN.matcher(nowhitespace).replaceAll("");
return slug.toLowerCase(Locale.ENGLISH);
}
public static void update(final Context context, final boolean force)
{
Runnable r = new Runnable()
{
public void run()
{
SharedPreferences prefs = LegacyJSONConfigFile.getPreferences(context);
long lastUpdate = prefs.getLong(LegacyJSONConfigFile.JSON_LAST_UPDATE, 0);
long now = System.currentTimeMillis();
int interval = Integer.parseInt(prefs.getString("config_json_refresh_interval", "3600"));
if (force || (interval > 0 && now - lastUpdate > 1000 * interval))
{
Editor edit = prefs.edit();
EncryptionManager.getInstance().setConfigurationReady(false);
LegacyJSONConfigFile.updateFromOnline(context);
edit.putLong(LegacyJSONConfigFile.JSON_LAST_UPDATE, now);
edit.commit();
if (LegacyJSONConfigFile._sharedFile != null)
LegacyJSONConfigFile._sharedFile.updateTriggers(context);
}
else if (lastUpdate != 0)
{
String lastHash = prefs.getString(LegacyJSONConfigFile.JSON_LAST_HASH, null);
if (lastHash != null && lastHash.trim().length() > 0)
EncryptionManager.getInstance().setConfigurationReady(true);
}
}
};
try
{
Thread t = new Thread(r);
t.start();
}
catch (OutOfMemoryError e)
{
LogManager.getInstance(context).logException(e);
}
}
protected String content()
{
return this.parameters.toString();
}
protected boolean isValid()
{
if (this.content().length() > Preferences.MAX_VALUE_LENGTH)
return false;
return true;
}
}